/**
 * @file useForm
 * @author  fanxl
 */

import {
    cloneDeep,
    get,
    isFunction,
    isPlainObject,
    isEmpty,
    isArray,
    flatten,
    compact,
    isObject,
    isNil,
    reduce,
    isString,
    set as lodashSet,
    uniq,
    isBoolean,
    startsWith,
    sortBy,
    indexOf,
    forEach,
    filter,
    find,
    zip,
    fromPairs,
    pickBy,
    map,
    mergeWith,
    values as lodashValues
} from 'lodash-es';
import {type InjectionKey, inject, provide, ref, unref, reactive, computed, onMounted} from 'vue';
import type {ValidateResult} from '../validator/interface';
import {convertLeafValue, createRandomChars, genUniqMeshedValue, isEmptyAlternativeValue} from './utils';
import {getChildSpecInitValue, createItem, type FormItem, sortPathBySpec} from '../util';
import type {
    UseForm,
    UseFormProps,
    FieldRegistryType,
    ValuesState,
    TouchedState,
    ErrorState,
    NestedObjectTypeValue,
    RegisterOptions,
    FieldLoadingState,
    ShallowObjectType,
    FieldSettingOptions,
    SimpleDataTypeValue
} from './types';
import {useStateBag} from './useStateBag';
import {getFieldPath} from '../path';
import type {UnionSpecItem} from '../interface';
import mitt from 'mitt';

export const FORM_CONTEXT: InjectionKey<UseForm> = Symbol('legoContext');

const getParentPath = (path: string) => path.split('.').slice(0, -1).join('.');

export const useFormInject = (): UseForm => {
    const form = inject(FORM_CONTEXT);
    if (!form) {
        throw new Error('`useFormInject` should be used within `useForm` component');
    }
    return form;
};

export const useFormProvide = (form: UseForm) => {
    provide(FORM_CONTEXT, form);
};

const emptyState = () => ({});

export function useForm({
    initialValues = {},
    initialPreviewValues = {},
    validateOnChange = true,
    validateOnBlur = true,
    /** 预留一个form的额外配置字段，透传进入Context */
    options = {}
}: UseFormProps): UseForm {
    const $EventBus = mitt();
    const rawInitialValues = unref(initialValues);
    const rawinitialPreviewValues = unref(initialPreviewValues);
    const initialTouched = convertLeafValue(rawInitialValues);
    const rawInitialTouched = unref(initialTouched);

    const fieldsBag = useStateBag<FieldRegistryType, any>(emptyState());
    const valuesBag = useStateBag<ValuesState, ValuesState>(rawInitialValues || emptyState());
    const touchedBag = useStateBag<TouchedState, NestedObjectTypeValue<boolean>>(rawInitialTouched || emptyState());
    const listCharsBag = useStateBag<ErrorState, Array<string>>(emptyState());
    const errorsBag = useStateBag<ErrorState, Array<string>>(emptyState());

    // fields
    const fieldRegistry = fieldsBag.state;
    const getFieldRegistry = fieldsBag.getFieldState(true);
    const setFieldRegistry = fieldsBag.setFieldState(true);
    const setBatchFieldsRegistry = fieldsBag.setState;

    // values
    const values = valuesBag.state;
    const getFieldValue = valuesBag.getFieldState(false);
    const setFieldValue = (fieldName: string, value: ValuesState, options?: FieldSettingOptions) => {
        const {shouldValidate, ifUpdateCharsAboutList = true, ifTriggerFormChange = true} = options || {};
        const fieldRegistryData = getFieldRegistry(fieldName);
        // 这里兼容下不存在的节点，支持往不存在的节点插数据
        if (fieldRegistryData) {
            const {spec} = fieldRegistryData;
            const {name} = spec as UnionSpecItem;
            const mergeValues = getChildSpecInitValue(spec, {[name]: value}, unref(values), fieldName);
            valuesBag.setFieldState(false, fieldName, get(mergeValues, name));
        } else {
            valuesBag.setFieldState(false, fieldName, value);
        }
        if (ifTriggerFormChange) {
            $EventBus.emit('formChange', {
                target: createItem(fieldName, getFieldValue, setFieldValue, getFieldRegistry)
            });
        }
        ifUpdateCharsAboutList && recurseUpdateChars(fieldName, value);
        const willValidate = shouldValidate === undefined ? validateOnChange : shouldValidate;
        return willValidate ? validateForm() : Promise.resolve();
    };
    const deleteFieldValue = valuesBag.deleteFieldState(false);

    // touched
    const touched = touchedBag.state;
    const getFieldTouched = touchedBag.getFieldState(false);
    const deleteFieldTouched = touchedBag.deleteFieldState(false);

    // ListKey
    const listChars = listCharsBag.state;
    const getListChars = listCharsBag.getFieldState(true);
    const setListChars = listCharsBag.setFieldState(true);
    const deleteListChars = listCharsBag.deleteFieldState(true);
    const setBatchListChars = listCharsBag.setState;

    /**
     * 允许三种设置方式
     * 1、fieldName对应的不是叶子结点，touched为Boolean类型；
     *    -> 把fieldName节点的结构按照values做出同构数据，全部赋值为touched；
     * 2、fieldName对应的不是叶子结点，touched为Object类型；
     *    -> 直接把touched赋值给fieldName的节点；
     * 3、fieldName对应的是叶子节点，touched必须是Boolean类型；
     *    -> touched若不是布尔类型，报错；
     */
    const setFieldTouched = (fieldName: string, touched: NestedObjectTypeValue<boolean>, shouldValidate?: boolean) => {
        const touchValues = getFieldTouched(fieldName);
        if (isBoolean(touchValues)) {
            if (!isBoolean(touched)) {
                throw new Error(`设置节点${fieldName}的touched属性为${JSON.stringify(touched)}失败`);
            }
            touchedBag.setFieldState(false, fieldName, touched);
        } else {
            const nodeValues = getFieldValue(fieldName);
            const touchValues = isObject(touched) ? touched : convertLeafValue(nodeValues, touched);
            touchedBag.setFieldState(false, fieldName, touchValues);
        }
        const willValidate = shouldValidate === undefined ? validateOnBlur : shouldValidate;
        return willValidate ? validateForm() : Promise.resolve();
    };
    const setFieldsTouched = touchedBag.setState;

    // error
    const errors = errorsBag.state;
    const getFieldError = errorsBag.getFieldState(true);
    const setErrors = errorsBag.setState;

    const validating = ref(false);

    // 注册字段
    const registerField = (fieldName: string, registerOptions: RegisterOptions) => {
        setFieldRegistry(fieldName, registerOptions);
    };

    // 注销字段
    const unregisterField = (fieldName: string) => {
        if (unref(fieldRegistry)[fieldName]) {
            delete unref(fieldRegistry)[fieldName];
        } else {
            console.error('delete fieldRegistry fail', fieldName);
        }
        if (getListChars(fieldName)) {
            deleteListChars(fieldName);
        }
    };

    const recurseUpdateChars = (fieldName: string, value: ValuesState) => {
        if (typeof value !== 'object') {
            return;
        }
        if (Array.isArray(value)) {
            const chars = value.map(() => createRandomChars());
            setListChars(fieldName, chars);
            value.forEach((v, i) => {
                recurseUpdateChars(`${fieldName}.[${i}]`, v);
            });
        }
        if (isPlainObject(value)) {
            Object.keys(value).forEach((key) => {
                recurseUpdateChars(`${fieldName}.${key}`, value[key]);
            });
        }
    };

    const runSingleFieldLevelValidation = (
        fieldName: string,
        value: any,
        parentValue: any
    ): Promise<Array<ValidateResult>> => {
        const rawFieldRegistry = unref(fieldRegistry);
        const validateFn = rawFieldRegistry[fieldName].validate;
        if (isFunction(validateFn)) {
            return Promise.resolve(validateFn(value, parentValue));
        }
        return Promise.resolve([]);
    };

    const runFieldLevelValidations = () => {
        validating.value = true;
        const fieldKeysWithValidation = reduce<FieldRegistryType, Array<string>>(
            unref(fieldRegistry),
            (result, value, key) => {
                if (isFunction(value.validate) && !value.isRemove) {
                    result.push(key as unknown as string);
                }
                return result;
            },
            []
        );
        const fieldValidations = fieldKeysWithValidation.map((key) => {
            const getParentValue = (key: string) => {
                if (key.split('.').length < 2) {
                    return cloneDeep(unref(values));
                }
                const parentKey = getParentPath(key);
                return getFieldValue(parentKey);
            };
            return runSingleFieldLevelValidation(key, getFieldValue(key), getParentValue(key));
        });

        return Promise.all(fieldValidations)
            .then((fieldErrorsList: Array<ValidateResult[]>): Record<string, Array<string>> => {
                const errList = zip(
                    fieldKeysWithValidation,
                    fieldErrorsList.map((items) => {
                        return filter(items, (it) => it !== true);
                    })
                );
                const errObj = pickBy(
                    pickBy(fromPairs(errList), (val) => {
                        return val !== true;
                    }),
                    (val) => {
                        return !isEmpty(val);
                    }
                );
                return errObj;

                // const handler = compose<
                //     [Array<string>, Array<string[]>],
                //     Array<Array<string | ValidateResult[]>>,
                //     Record<string, ValidateResult[]>,
                //     Record<string, string[]>,
                //     Record<string, string[]>
                // >(pickBy(negate(isEmpty)), pickBy(negate(eq(true))), fromPairs, zip);
                // const withoutTrue = map(filter(negate(eq(true))));
                // return handler(fieldKeysWithValidation, withoutTrue(fieldErrorsList));
            })
            .catch((err) => {
                throw err;
            })
            .finally(() => {
                validating.value = false;
            });
    };

    const setAllTouched = () => {
        setFieldsTouched(convertLeafValue(unref(values), true));
    };

    const mergeErrors = (target: Record<string, Array<string>>): Record<string, Array<string>> => {
        const inlineErrors = reduce<FieldRegistryType, Record<string, Array<string>>>(
            unref(fieldRegistry),
            (prev, value, key) => {
                if (!value.isRemove) {
                    const inlineError = Object.values(value?.inlineError || {}).flat();
                    if (inlineError.length) {
                        prev[key] = inlineError;
                    }
                }
                return prev;
            },
            {}
        );

        const customizer = (objValue: Record<string, Array<string>>, srcValue: Record<string, Array<string>>) => {
            if (isArray(objValue)) {
                return objValue.concat(srcValue);
            }
        };
        const allErrors = mergeWith(inlineErrors, target, customizer);
        forEach(allErrors, (value: Array<string>, key) => {
            if (isArray(value)) {
                allErrors[key] = uniq(value);
            }
        });
        return delegateError(allErrors);
    };

    const validateForm = () => {
        return runFieldLevelValidations().then((fieldErrors) => {
            const allErrors = mergeErrors(fieldErrors);
            setErrors(allErrors);
            return allErrors;
        });
    };

    /**
     * 隐藏节点的error，委托到最近一级的非隐藏的祖先节点去
     * @param errors 所有Error
     * @returns Error
     */
    const delegateError = (errors: Record<string, Array<string>>): Record<string, Array<string>> => {
        const dumpErrors = cloneDeep(errors);
        Object.keys(dumpErrors).forEach((key) => {
            let delegateKey = key;
            while (getInlineState([delegateKey, 'display']) === 'none' || !getFieldRegistry(delegateKey)) {
                if (!delegateKey.includes('.')) {
                    console.error(`[SPEC数据异常]：${delegateKey}的display属性是none`);
                    return;
                }
                delegateKey = getParentPath(delegateKey);
            }
            if (delegateKey === key) {
                return;
            }
            const originDisplayName = getFieldRegistry(key)?.spec?.displayName || '隐藏选项';
            const currentError = originDisplayName + ':' + dumpErrors[key].join(',');
            const newErrors = uniq((dumpErrors[delegateKey] || []).concat(currentError));
            dumpErrors[delegateKey] = newErrors;
        });
        return dumpErrors;
    };

    /**
     * 监听某些field项是否通过校验
     * @param fieldOrFields fieldName
     * @param isPrefix 是否是前缀匹配，默认false
     * @param isShowErrors 是否显示错误
     * @returns Promise<boolean>
     */
    const listenFieldsValidate = (
        fieldOrFields: string | Array<string>,
        isPrefix: boolean = false,
        isShowErrors: boolean = false
    ) => {
        const fields = isString(fieldOrFields) ? [fieldOrFields] : fieldOrFields;
        if (isShowErrors) {
            fields.forEach((fieldName) => {
                setFieldTouched(fieldName, true, false);
            });
        }

        return runFieldLevelValidations().then((allErrors) => {
            const errors = Object.keys(allErrors).filter((key) =>
                fields.find((v) => (isPrefix ? key.startsWith(v) : key === v))
            );
            return isEmpty(errors);
        });
    };

    // 根据path去拿fieldRegistry里，某个Field对应的值
    const getInlineState = (path: Array<string>) => {
        if (!isArray(path) || isEmpty(path)) {
            throw new Error('入参必须传入操作的路径');
        }
        const [fieldName, dataType = '', extName = ''] = path;
        const fieldRegistryState = getFieldRegistry(fieldName);
        if (fieldRegistryState) {
            return get(fieldRegistryState, compact([dataType, extName]));
        }
        return [];
    };

    const setInlineState = (path: Array<string>, value: Array<string> | FieldLoadingState) => {
        const [fieldName, dataType, extName] = path;
        const fieldRegistryState = cloneDeep(getFieldRegistry(fieldName)) as Record<string, any>;
        if (fieldRegistryState) {
            const specificTypeData = Object.assign(fieldRegistryState[dataType], {[extName]: value});
            Object.assign(fieldRegistryState, {[dataType]: specificTypeData});
            form.setFieldRegistry(fieldName, fieldRegistryState);
        }
    };

    /**
     * 手动注入表单error
     * @param fieldName 节点路径
     * @param key error的key
     * @param error error data
     */
    const setInlineError = (fieldName: string, key: string, error: Array<string>) => {
        form.setInlineState([fieldName, 'inlineError', key], error);
        return validateForm();
    };

    const removeInlineError = (fieldName: string, key: string) => {
        form.setInlineState([fieldName, 'inlineError', key], []);
        return validateForm();
    };

    // 设置表达式状态 isHidden & isRemove
    const setInlineExpressState = (fieldName: string, type: 'isHidden' | 'isRemove', value: boolean) => {
        const targetFieldRegistry = cloneDeep(getFieldRegistry(fieldName));
        if (targetFieldRegistry) {
            targetFieldRegistry[type] = value;
            form.setFieldRegistry(fieldName, targetFieldRegistry);
        }
    };

    /**
     * 全局的Loading状态
     */
    const globalLoading = computed(() => {
        /**
         * 搜寻fieldRegistry下，loadingState里有isBlock && isLoading的
         */
        // const handler = compose<
        //     [FieldRegistryType],
        //     RegisterOptions[],
        //     ShallowObjectType<FieldLoadingState>[],
        //     FieldLoadingState[][],
        //     FieldLoadingState[],
        //     FieldLoadingState | undefined
        // >(
        //     find((v: FieldLoadingState) => v?.isBlock && v?.isLoading),
        //     flatten,
        //     map(v => lodashFpValues(v)),
        //     pluck('loadingState'),
        //     filter((v: RegisterOptions) => !v.isRemove)
        // );
        // return !!handler(unref(fieldRegistry));

        return !!find(
            flatten(
                map(
                    filter(unref(fieldRegistry), (v: RegisterOptions) => !v.isRemove),
                    (item) => lodashValues(item.loadingState)
                )
            ),
            (v: FieldLoadingState) => v?.isBlock && v?.isLoading
        );
    });

    const isNilOrEmptyString = (fieldValue: any) => isNil(fieldValue) || (isString(fieldValue) && !fieldValue);

    const previewValues = computed(() => {
        const clonePreviewValues = cloneDeep(unref(values));
        const rawFields = unref(fieldRegistry);
        Object.keys(rawFields).forEach((fieldName) => {
            const currentFieldRegistry = rawFields[fieldName];
            const fieldValue = get(clonePreviewValues, fieldName);
            if (!currentFieldRegistry.isRemove && !isObject(fieldValue) && isNilOrEmptyString(fieldValue)) {
                const fieldPreviewValue = get(rawinitialPreviewValues, fieldName);
                lodashSet(
                    clonePreviewValues,
                    fieldName,
                    isNilOrEmptyString(fieldValue) ? fieldPreviewValue : fieldValue
                );
            }
        });
        return clonePreviewValues;
    });

    const meshedData = computed(() => {
        const collectionData: ShallowObjectType<SimpleDataTypeValue | ShallowObjectType<SimpleDataTypeValue>> = {};
        if (!options.hasHighlight) {
            return collectionData;
        }
        const clonePreviewValues = cloneDeep(unref(previewValues));
        const rawFields = unref(fieldRegistry);

        forEach(rawFields, (fieldOptions, fieldName) => {
            const previewData = get(clonePreviewValues, fieldName) as ShallowObjectType<
                SimpleDataTypeValue | ShallowObjectType<SimpleDataTypeValue>
            >;
            if (isObject(previewData)) {
                if (isEmpty(previewData)) {
                    collectionData[fieldName] = {};
                } else if (isEmptyAlternativeValue(previewData, fieldOptions.spec)) {
                    collectionData[fieldName] = previewData as ShallowObjectType<SimpleDataTypeValue>;
                }
                return;
            }
            collectionData[fieldName] = genUniqMeshedValue(fieldOptions.spec, previewData);
        });
        return collectionData;
    });

    // 获取隐藏节点
    const getAllHiddenNodes = () => {
        return reduce<FieldRegistryType, Array<string>>(
            unref(fieldRegistry),
            (result, value, key) => {
                const {isRemove, isHidden, display} = value;
                // @ts-ignore
                if (isRemove || isHidden || display === 'none') {
                    result.push(key as unknown as string);
                }
                return result;
            },
            []
        );
    };

    if (typeof window === 'object') {
        onMounted(() => {
            $EventBus.emit('formChange', {target: null});
        });
    }
    // 适配iris sdk 依赖的老接口
    const getItemByPath = (path: string, item: FormItem): FormItem[] => {
        const originPath = item?.path || '';
        const fieldName = getFieldPath(originPath, path);
        if (fieldName) {
            return [createItem(fieldName, getFieldValue, setFieldValue, getFieldRegistry)];
        }
        return [];
    };
    // 适配iris sdk 依赖的老接口
    const getItemsByFunc = (func: (curItem: FormItem) => FormItem | undefined, item: FormItem): FormItem[] => {
        let prefixPath = '';
        if (item?.path) {
            prefixPath = `${item.path}.`;
        }
        let sortedSpecPaths: string[] = [];
        if (item?.spec) {
            sortedSpecPaths = sortPathBySpec(item.spec, item.path.split('.').slice(0, -1).join('.'));
        }
        const formItems = Object.keys(unref(fieldRegistry))
            .filter((path) => !prefixPath || startsWith(path, prefixPath))
            .map((path) => {
                const currentItem = createItem(path, getFieldValue, setFieldValue, getFieldRegistry);
                const funcResult = func(currentItem);
                return funcResult ? currentItem : null;
            })
            .filter((item) => !isNil(item)) as FormItem[];
        return sortBy(formItems, (formItem) => {
            return indexOf(sortedSpecPaths, formItem.path);
        });
    };

    const form = reactive({
        validating,
        options,

        initialValues,
        values,
        getFieldValue,
        setFieldValue,
        deleteFieldValue,

        meshedData,

        previewValues,

        touched,
        getFieldTouched,
        deleteFieldTouched,
        setFieldTouched,
        setFieldsTouched,

        listChars,
        getListChars,
        setListChars,
        deleteListChars,

        errors,
        getFieldError,
        setErrors,

        registerField,
        unregisterField,

        validateForm,
        setAllTouched,

        fieldRegistry,
        getFieldRegistry,
        setFieldRegistry,
        setBatchFieldsRegistry,
        setBatchListChars,

        getInlineState,
        setInlineState,
        setInlineError,
        removeInlineError,
        setInlineExpressState,
        getAllHiddenNodes,

        listenFieldsValidate,
        $EventBus,
        globalLoading,
        // 适配iris sdk 依赖的接口
        getItemByPath,
        getItemsByFunc
    });

    return form;
}
